﻿using ProtoBuf;
using System;
using T0yK4T.IO;
using System.Collections.Generic;
using T0yK4T.Data;
using T0yK4T.Tools;
using MongoDB;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
//using MongoDB.Attributes;

namespace ToyChat.Server
{
    /// <summary>
    /// This class is used as a basic representation of a channel in ToyChat
    /// </summary>
    [ProtoContract]
    public class Channel : ComponentBase, IChannel
    {
        private Dictionary<int, IUser> users = new Dictionary<int, IUser>();

        /// <summary>
        /// Empty Constructor - Doesn't do anything
        /// </summary>
        public Channel()
        {
        }

        /// <summary>
        /// Initializes a new <see cref="Channel"/> object and copies values from the specified <see cref="IChannel"/> to the newly created instance
        /// </summary>
        /// <param name="template">The <see cref="IChannel"/> to use as a template</param>
        public Channel(IChannel template)
        {
            this.ChannelID = template.ChannelID;
            this.ChannelName = template.ChannelName;
            this.IsHidden = template.IsHidden;
            this.MotD = template.MotD;
            this.Password = template.Password;
        }

        /// <summary>
        /// Sends the specified packet to -all- users currently in this channel
        /// </summary>
        /// <param name="packet"></param>
        public void Send(Packet packet)
        {
            foreach (IUser user in this.users.Values)
                user.Send(packet);
        }

        /// <summary>
        /// Gets or Sets the Logger that is used by this instance
        /// <para/>
        /// This field is "overriden" to exclude it from MongoDB
        /// </summary>
        [BsonIgnore]
        public new ILogger Logger
        {
            get { return base.Logger; }
            set { base.Logger = value; }
        }

        /// <summary>
        /// Adds the specified user to this channel
        /// </summary>
        /// <param name="user">The user to add</param>
        public void AddUser(IUser user)
        {
            if (!this.users.ContainsKey(user.UserID))
            {
                this.users.Add(user.UserID, user);
                base.LogInformation("User {0} joined channel {1}", user.Username, this.channelName);
            }
        }

        /// <summary>
        /// Removes the specified user from this channel
        /// </summary>
        /// <param name="user">The user to remove</param>
        public void RemoveUser(IUser user)
        {
            if (this.users.Remove(user.UserID))
                base.LogInformation("User {0} left channel {1}", user.Username, this.channelName);
        }

        /// <summary>
        /// Used by MongoDB
        /// </summary>
        [BsonId]
        public ObjectId _id { get; set; }

        /// <summary>
        /// The ID of this channel
        /// </summary>
        private int channelID = 0;

        /// <summary>
        /// Gets or Sets the ID of this channel
        /// </summary>
        public int ChannelID
        {
            get { return this.channelID; }
            set { this.channelID = value; }
        }

        /// <summary>
        /// Indicates if this channel is hidden
        /// </summary>
        private bool isHidden = false;

        /// <summary>
        /// Gets or Sets a value indicating wether or not this channel is hidden
        /// </summary>
        public bool IsHidden
        {
            get { return this.isHidden; }
            set { this.isHidden = value; }
        }
    
        /// <summary>
        /// Indicates the name of this channel
        /// </summary>
        private string channelName = string.Empty;
        /// <summary>
        /// Gets or Sets the name of this channel
        /// </summary>
        public string ChannelName
        {
            get { return this.channelName; }
            set { this.channelName = value ?? "NULL"; }
        }

        /// <summary>
        /// Indicates the motd of this channel
        /// </summary>
        private string motd = string.Empty;

        /// <summary>
        /// Gets or Sets the Message of the Day of this channel
        /// </summary>
        public string MotD
        {
            get { return this.motd; }
            set { this.motd = value ?? string.Empty; }
        }

        /// <summary>
        /// Indicates the password of this channel
        /// </summary>
        private string password = string.Empty;
        /// <summary>
        /// Gets or Sets the password for this channel
        /// </summary>
        public string Password
        {
            get { return this.password; }
            set { this.password = value ?? string.Empty; }
        }

        /// <summary>
        /// Gets the number of users currently in this channel
        /// <para/>
        /// Password protected channels will always return -1
        /// </summary>
        [BsonIgnore]
        public int UserCount
        {
            get { return (string.IsNullOrEmpty(this.password) ? this.users.Count : -1); }
        }

        /// <summary>
        /// Gets a reference to the list of users currently in this channel
        /// </summary>
        [BsonIgnore]
        public IEnumerable<IUser> Users { get { return this.users.Values; } }

        /// <summary>
        /// Removes the specified <see cref="IUser"/> from this channel (If the user is currently in this channel)
        /// </summary>
        /// <param name="user">The user that should be remove</param>
        /// <param name="reason">The reason for the removal</param>
        public void RemoveUser(IUser user, string reason)
        {
            if (this.users.Remove(user.UserID))
                base.LogInformation("User {0} left channel {1} - {2}", user.Username, this.channelName, reason);
        }

        /// <summary>
        /// Checks if the specified user is currently in this channel
        /// </summary>
        /// <param name="user">The user to check for</param>
        /// <returns>True if the user is currently in the channel, otherwise false</returns>
        public bool ContainsUser(IUser user)
        {
            if (user == null)
                return false;
            return this.users.ContainsKey(user.UserID);
        }
    }
}